package com.caffeinatedbliss.nanowrimo;

import java.awt.*;
import java.awt.image.BufferedImage;
import java.text.MessageFormat;
import java.util.*;

public class CalendarGraph implements DynamicImageGenerator {
  private static final int CURVE_RADIUS = 8;
  public static final int WIDTH = 140;
  public static final int HEIGHT = 150;

  public static final Color LIGHTEST_GREY = new Color(0xf0, 0xf0, 0xf0);
  public static final Color NOT_SO_LIGHTEST_GREY = new Color(0xbb, 0xbb, 0xdd);
  private static final GradientPaint GRADIENT = new GradientPaint(CURVE_RADIUS, CURVE_RADIUS,
    LIGHTEST_GREY, 2 * CURVE_RADIUS, CURVE_RADIUS * 7, NOT_SO_LIGHTEST_GREY, false);

  public static final Color GREY_BORDER = new Color(0xbb, 0xbb, 0xbb);
  public static final Color NAME_COLOR = new Color(0x66, 0x66, 0x66);
  public static final Color DK_GREY_BORDER = new Color(0x99, 0x99, 0x99);
  private static final GradientPaint OUTSIDE_GRADIENT = new GradientPaint(CURVE_RADIUS, CURVE_RADIUS,
    GREY_BORDER, 2 * CURVE_RADIUS, CURVE_RADIUS * 7, DK_GREY_BORDER, false);

  public static final Color GRAPH_BACKGROUND = new Color(0xee, 0xee, 0xee);
  public static final Color LT_RED_INK = new Color(0xff, 0xcc, 0xcc);
  public static final Color LT_GREEN_INK = new Color(0xcc, 0xff, 0xcc);
  public static final Color GREEN_INK = new Color(0x00, 0xcc, 0x00);

  private BufferedImage graphImage;
  private static final Font DAY_TEXT_FONT = new Font("dialog", Font.PLAIN, 9);
  private static final Font NAME_TEXT_FONT = new Font("dialog", Font.PLAIN, 10);
  private static final Font TEXT_FONT = new Font("dialog", Font.BOLD, 11);
  private NanowrimoComputer computer;

  public CalendarGraph(int[] amounts, int goal, String user, int year, String wordcountFormat) {
    computer = new NanowrimoComputer(amounts, goal);
    init(user, year, wordcountFormat);
  }

  private void init(String user, int year, String wordcountFormat) {
    graphImage = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_ARGB);
    Graphics2D g = (Graphics2D) graphImage.getGraphics();

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
    drawFilledRectangleWithCurvedCorners(g);
    double percentComplete = computer.getPercentComplete();
    int daysLeft = computer.getDaysLeftNaNoWriMo();
    boolean started = (daysLeft != 30 || percentComplete != 0.0);
    drawWordCount(g, wordcountFormat, computer.getCurrent(), percentComplete, daysLeft);
    drawName(g, user);

    g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);

    drawPercentCompleteBar(g, percentComplete);

    drawDetailedBarChart(g, started, computer.getHistoryCounts(), computer.getExpectedPerDayAmount(),
      computer.getOutliers(), year);

    g.dispose();
    computer = null;
  }

  private void drawDetailedBarChart(Graphics2D g, boolean started, int[] historyCounts, int[] expectedDayAmount, boolean[] outliers, int year) {
    int edgePadding = 2;
    int boxWidth = 15;
    int boxHeight = 11;
    int boxGap = 3;
    int daysInWeek = 7;
    int totalWeeks = 5;

    int leftEdgePosition = 4;
    int calendarTopPosition = 64;

    int totalWidth = (daysInWeek * boxWidth) + (daysInWeek - 1) * boxGap + 4 + 2 * edgePadding;
    int totalHeight = totalWeeks * boxHeight + 4 * boxGap + 4 + 2 * edgePadding;

    drawBackgroundBox(g, leftEdgePosition, calendarTopPosition - 17, totalWidth, boxHeight + 4);
    drawBackgroundBox(g, leftEdgePosition, calendarTopPosition, totalWidth, totalHeight);

    g.setFont(DAY_TEXT_FONT);
    FontMetrics fm = g.getFontMetrics(DAY_TEXT_FONT);
    int ascent = fm.getAscent();
    int descent = fm.getDescent();
    int totalLineHeght = ascent + descent;

    String[] names = {"Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"};
    g.setColor(DK_GREY_BORDER);
    for (int i = 0; i < names.length; i++) {
      g.drawString(names[i], leftEdgePosition + 6 + i * (boxWidth + boxGap),
        calendarTopPosition - 17 + totalLineHeght);
    }

    Calendar gcal = new GregorianCalendar();
    gcal.set(Calendar.YEAR, year);
    gcal.set(Calendar.MONTH, Calendar.NOVEMBER);
    gcal.set(Calendar.DAY_OF_MONTH, 1);
    int start = gcal.get(Calendar.DAY_OF_WEEK);
    int dayCount = 0;
    for (int row = 0; row < totalWeeks; row++) {
      for (int col = 0; col < daysInWeek; col++) {
        if (row * daysInWeek + col + 1 >= start && dayCount < 30) {
          g.setColor(Color.WHITE);
          g.fillRect(leftEdgePosition + 4 + col * (boxWidth + boxGap),
            calendarTopPosition + 4 + row * (boxHeight + boxGap), boxWidth, boxHeight);
          g.setColor(GREY_BORDER);
          g.drawRect(leftEdgePosition + 4 + col * (boxWidth + boxGap),
            calendarTopPosition + 4 + row * (boxHeight + boxGap), boxWidth, boxHeight);

          int written = 0;
          boolean useRed = false;
          if (dayCount < historyCounts.length) {
            written = historyCounts[dayCount];
            useRed = true;
          }
          if (started) {
            if (expectedDayAmount[dayCount] > written) {
              if (useRed) {
                g.setColor(outliers[dayCount] ? Color.RED : LT_RED_INK);
                g.fillRect(leftEdgePosition + 6 + col * (boxWidth + boxGap),
                  calendarTopPosition + 6 + row * (boxHeight + boxGap), boxWidth - 3, boxHeight - 3);
              }
            } else {
              g.setColor(outliers[dayCount] ? GREEN_INK : LT_GREEN_INK);
              g.fillRect(leftEdgePosition + 6 + col * (boxWidth + boxGap),
                calendarTopPosition + 6 + row * (boxHeight + boxGap), boxWidth - 3, boxHeight - 3);
            }
          }

          dayCount++;
        }
      }
    }
  }

  private void drawBackgroundBox(Graphics2D g, int x, int y, int totalWidth, int totalHeight) {
    g.setColor(GRAPH_BACKGROUND);
    g.fillRect(x, y, totalWidth, totalHeight);
    g.setColor(GREY_BORDER);
    g.drawRect(x, y, totalWidth, totalHeight);
    g.setColor(Color.WHITE);
    g.drawRect(x + 1, y + 1, totalWidth - 2, totalHeight - 2);
  }

  private void drawName(Graphics2D g, String name) {
    if (name != null) {
      g.setColor(NAME_COLOR);
      drawCenteredText(g, NAME_TEXT_FONT, name, -3);
    }
  }

  private void drawPercentCompleteBar(Graphics2D g, double percentComplete) {
    int green = (int) (1 + (WIDTH - 15) * percentComplete / 100.0);
    g.setColor(GREY_BORDER);
    g.drawRect(5, 15, WIDTH - 11, 10);
    g.setColor(GRAPH_BACKGROUND);
    g.fillRect(6, 16, WIDTH - 13, 9);
    g.setColor(GREEN_INK);
    g.fillRect(7, 17, green, 7);
  }

  private void drawWordCount(Graphics2D g, String wordcountFormat, int current, double percentComplete, int daysLeftNaNoWriMo) {
    g.setColor(NAME_COLOR);
    String text = MessageFormat.format(wordcountFormat, current, daysLeftNaNoWriMo, percentComplete);
    drawCenteredText(g, TEXT_FONT, text, 25);
  }

  private void drawCenteredText(Graphics g, Font textFont, String aTextString, int yPos) {
    g.setFont(textFont);
    FontMetrics fm = g.getFontMetrics(textFont);
    int ascent = fm.getAscent();
    int descent = fm.getDescent();
    int totalLineHeght = ascent + descent;
    int wordWidth = fm.stringWidth(aTextString);
    int leftEdge = (WIDTH - wordWidth) / 2;
    g.drawString(aTextString, leftEdge, yPos + totalLineHeght);
  }

  private void drawFilledRectangleWithCurvedCorners(Graphics2D g) {
    g.setPaint(GRADIENT);
    g.fillRoundRect(0, 0, WIDTH - 1, HEIGHT - 1, 2 * CURVE_RADIUS, 2 * CURVE_RADIUS);

    g.setPaint(OUTSIDE_GRADIENT);
    g.drawRoundRect(0, 0, WIDTH - 1, HEIGHT - 1, 2 * CURVE_RADIUS, 2 * CURVE_RADIUS);
  }

  public BufferedImage getGraphImage() {
    return graphImage;
  }
}
